/* File: ActStrategy.java
 * Purpose: Defines an organism's algorithm for determining which action to take
 * Author: Team Karp
 * Date: 12/20/07
 * Revisions: 12/20/07, Adam Patterson - Initial Version
 *           [2008-01-22, Adam Patterson - Implemented scripting function]
 * Additional Requirements: 
 */
package protovitae.data;

import java.util.ArrayList;
import javax.script.*;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * @version 1.0
 * @created 20-Dec-2007 12:30:00
 */
public class ActStrategy extends GeneActionStrategy {

    /**
     * Constructor
     */
    public ActStrategy() {

//        StringBuilder sb = new StringBuilder();
//        sb.append("var v1 = Math.random();\n");
//        sb.append("var targs = environment.getNearestOrganisms(parent);\n");
//        sb.append("var myTarg = Math.round((targs.size*Math.random()/3)-1);\n");
//        sb.append("if(myTarg<0){myTarg = 0;}\n");
//        sb.append("if(v1<.2){\n");
//        sb.append("vote = parent.getActionOrdinalFromString(\"MOVE\");\n");
//        sb.append("}else if(v1<.4){\n");
//        sb.append("vote = parent.getActionOrdinalFromString(\"DANCE\");\n");
//        sb.append("} else if(v1<.6 && targs.size>0){\n");
//        sb.append("vote = parent.getActionOrdinalFromString(\"MATE\");\n");
//        sb.append("target = targs[myTarg];\n");
//        sb.append("}else if(v1<.8 && targs.size>0){\n");
//        sb.append("vote = parent.getActionOrdinalFromString(\"ATTACK\");\n");
//        sb.append("target = targs.get[myTarg];\n");
//        sb.append("}else if(v1<=1.0 && targs.size>0){\n");
//        sb.append("vote = parent.getActionOrdinalFromString(\"EAT\");\n");
//        sb.append("target = targs.get[myTarg];\n");
//        sb.append("}else{\n");
//        sb.append("vote = parent.getActionOrdinalFromString(\"DANCE\");\n");
//        sb.append("}\n");
//        
//        try{
//            this.parse(sb.toString());
//        }catch(Exception e){
//            e.printStackTrace();
//        }
    }

    /**
     * This method is called to execute the GeneActionStrategy.
     * @param information - the simulation environment
     * @param parent - the owning ConcreteOrganism
     * @return the Organism.ACTION_TYPE action to take
     */
    public Object execute(Object information, ConcreteOrganism parent) {
        Organism.ACTION_TYPE vote = null;
        ConcreteOrganism target = parent;
        ArrayList formedVote = new ArrayList();
        boolean nocode = true;

        if (actionScript != null) {
            try {
                ArrayList voteInfo = (ArrayList) actionScript.invokeFunction("act", (Simulation) information, parent, new ArrayList(), this);
                if (voteInfo.size() > 1 && voteInfo.get(0) != null && voteInfo.get(1) != null) {
                    Double ret1 = (Double) voteInfo.get(0);
                    vote = parent.getActionType(ret1.intValue());
                    target = (ConcreteOrganism)voteInfo.get(1);
                    nocode = false;
                }
            } catch (NoSuchMethodException nsme) {
                nsme.printStackTrace();
                vote = null;
            } catch (ScriptException se) {
                se.printStackTrace();
                vote = null;
            } catch (Exception e) {
                e.printStackTrace();
                vote = null;
            }
        }
        if (nocode) {
            double i = Math.random();

            int brawn = parent.getAttribute(Organism.ATTRIBUTE_TYPE.BRAWN).getCurrent();
            int age = parent.getAttribute(Organism.ATTRIBUTE_TYPE.AGE).getCurrent();
            int attraction = parent.getAttribute(Organism.ATTRIBUTE_TYPE.ATTRACTION).getCurrent();
            int caution = parent.getAttribute(Organism.ATTRIBUTE_TYPE.CAUTION).getCurrent();
            int fortitude = parent.getAttribute(Organism.ATTRIBUTE_TYPE.FORTITUDE).getCurrent();
            int health = parent.getAttribute(Organism.ATTRIBUTE_TYPE.HEALTH).getCurrent();

            double mateChance = 0.06 * (1.0 / (brawn + 1));
            double danceChance = (0.10 * (1.0 / brawn)) + 0.01;

            double attackChance = 0.10 - mateChance;
            double eatChance = 0.20 * (1.0 / (health));

            double moveChance = 1.0 - (mateChance + danceChance + attackChance + eatChance);

            //System.out.println("die: " + i + ", mate:" + mateChance + ", dance: " + danceChance + ", attack: " + attackChance + ", eatChance: " + eatChance + ", move: " + moveChance);

            if (i < mateChance) {
                vote = Organism.ACTION_TYPE.MATE;
            } else if (i < mateChance + danceChance) {
                vote = Organism.ACTION_TYPE.DANCE;
            } else if (i < mateChance + danceChance + attackChance) {
                vote = Organism.ACTION_TYPE.ATTACK;
            } else if (i < mateChance + danceChance + attackChance + eatChance) {
                vote = Organism.ACTION_TYPE.EAT;
            } else {
                vote = Organism.ACTION_TYPE.MOVE;
            }
            target = ((Simulation) information).getNearestOrganism(parent);

        }

        formedVote.add(0, vote);
        formedVote.add(1, target); // This should be the target of our action. 
        // May need targeting routines in the ActStrategy
        // handle this.

        return formedVote;
    }

    /**
     * 
     * @param parent
     */
    public void save(Element parentElm, Document doc) {
        Element act = doc.createElement("act");

        Element xmlcode = doc.createElement("code");
        xmlcode.appendChild(doc.createTextNode(code));
        act.appendChild(xmlcode);

        //Write genetic byte array
        Element gene = doc.createElement("genedata");
        for (Byte thebyte : geneticInformation) {
            Element belm = doc.createElement("b");
            gene.appendChild(doc.createTextNode(thebyte.toString()));
            gene.appendChild(belm);
        }
        act.appendChild(gene);

        parentElm.appendChild(act);
    }

    public void load(Element input) {
        try {
            NodeList actroot = input.getElementsByTagName("act");
            NodeList actcode = ((Element) actroot.item(0)).getElementsByTagName("code");
            NodeList actdata = ((Element) actroot.item(0)).getElementsByTagName("genedata");

            parse(actcode.item(0).getTextContent()); //Calling parse will throw an exception for us if bad code. This is good.
            for (int i = 0; i < actdata.getLength(); i++) {
                if (actdata.item(i).getNodeName().equals("b")) {
                    geneticInformation.add(new Byte(Byte.parseByte(actdata.item(i).getTextContent())));
                }
            }
        } catch (Exception e) {
            e.printStackTrace(); //A bug occured
        }
    }

    /**
     * This parses the code string and stores it in param code.
     * @param code
     * @throws javax.script.ScriptException
     */
    public void parse(String code) throws ScriptException {
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine jsEngine = mgr.getEngineByName("JavaScript");

        //load the code into a function with parameters to use
        String scriptedFunctionality = "";
        scriptedFunctionality += "function act(environment,parent,ret_list,gene) {\n";
        scriptedFunctionality += "var vote = 0;\n";
        scriptedFunctionality += "var target = null;\n";
        scriptedFunctionality += code;
        scriptedFunctionality += "ret_list.add(vote);\n";
        scriptedFunctionality += "ret_list.add(target);\n";
        scriptedFunctionality += "return ret_list;\n";
        scriptedFunctionality += "}\n";

        if (code != null && code.length() > 0) {
            jsEngine.eval(scriptedFunctionality);
            actionScript = (Invocable) jsEngine;
        } else {
            actionScript = null;
        }
        this.code = code;
    }
}
